##################################################################
# PyngFM is a Python implementation of Ping.fm API.
#
# Version: 1.0
# Author: Andrea Grandi <a _DOT_ grandi _AT_ gmail _DOT_ com>
# License: GPL2
# Website: http://pyngfm.googlecode.com
#
# Note: this code is inspired to PHPingFM project.
##################################################################
import urllib
import urllib2
try:
    from xml.etree import ElementTree
except ImportError:
    from elementtree import ElementTree

from pyngfm.service import SystemService, UserService
from pyngfm.trigger import Trigger
from pyngfm.message import Message, Response, OK, FAIL
from pyngfm.exception import PingFMValueError, PingFMResponseError


BASE_URL = 'http://api.ping.fm/v1/'


class PingFMClient(object):

    def __init__(self, api_key, user_app_key):
        # Note that both of these keys are necessary to use the library.
        # User's app key
        self.user_app_key = user_app_key
        # The developer's API key
        self.api_key = api_key
        self.response = None

    def set_user_app_key(self, key):
        """
        Sets the User's APP key, if not already set.  It can also be used to
        change the key.
        """
        self.user_app_key = key

    def set_api_key(self, key):
        """
        Sets the API key, if not already set.  It can also be used to change
        the key.
        """
        self.api_key = key

    def __remote_call(self, service, parameters={}):
        """
        Call a method on the ping.fm server.

        @param service
          The end of the URL to fetch.  For example user.services will
          become http://api.ping.fm/v1/user.services when requesting from the
          server.
        @param parameters
          The parameters to pass in over POST.  The user's app key and the API
          key are automatically added.
        @return TODO: document me!
        """
        url = BASE_URL + str(service)
        if self.user_app_key:
            parameters['user_app_key'] = self.user_app_key
        if self.api_key:
            parameters['api_key'] = self.api_key
        encoded = urllib.urlencode(parameters)
        response = urllib2.urlopen(url, encoded).read()
        self.response = Response(response)
        if self.response.status == FAIL:
            raise PingFMResponseError(self.response.get_message())
        return self.response

    def user_validate(self):
        """
        Public API function: user.validate: validates the
        given user's app key.

        @return
          A boolean of whether the app key is correct.
        """
        response = self.__remote_call('user.validate')
        return response.status == OK

    def _set_common_parameters(self, **kwds):
        parameters = {}
        parameters['body'] = kwds.get('body')
        title = kwds.get('title')
        if title:
            parameters['title'] = title
        location = kwds.get('location')
        if location:
            parameters['location'] = location
        media = kwds.get('media')
        if media:
            parameters['media'] = media
        encoding = kwds.get('encoding')
        if encoding:
            parameters['encoding'] = encoding
        exclude = kwds.get('exclude')
        if exclude:
            parameters['exclude'] = exclude
        debug = kwds.get('debug')
        if isinstance(debug, string):
            debug = debug.lower()
        if debug in [True, 1, "1", "yes", "y", "true", "t"]:
            parameters['debug'] = 1
        checksum = kwds.get('checksum')
        if checksum:
            parameters['checksum'] = checksum
        return parameters

    def user_post(self, post_method, **kwds):
        """
        Public API function: user.post: posts a message to the user's Ping.fm
        services.

        @param post_method
          Posting method.  Either "blog", "microblog" or "status."
        @param body
          Message body.
        @param title
          Title of the posted message.  This will only appear if the specified
          service supports a title field.  Otherwise, it will be discarded.
        @param services
          A single service or array of services to post to.
          Default is all services set up for specified method.  If the posted
          method is not supported by service, the request will return an error.
        @param location
        @param media
        @param encoding
        @param exclude
        @param debug
        @param checksum
        """
        if not kwds.get('body'):
            raise PingFMValueError("The 'body' parameter must have a value.")
        parameters = self._set_common_parameters(**kwds)
        parameters['post_method'] = post_method
        services = kwds.get('services')
        if services:
            parameters['services'] = services
        response = self.__remote_call('user.post', parameters)
        return response.status

    def user_tpost(self, trigger, **kwds):
        """
        Public API function: user.tpost: posts a message to the user's Ping.fm
        services using one of their custom triggers.

        @param trigger
          Custom trigger the user has defined from the Ping.fm website.
        @param body
          Message body.
        @param title
          Title of the posted message.  This will only appear if the specified
          service supports a title field.  Otherwise, it will be discarded.
        @param location
        @param media
        @param encoding
        @param exclude
        @param debug
        @param checksum
        """
        if not body:
            raise PingFMValueError("The 'body' parameter must have a value.")
        parameters = self._set_common_parameters(**kwds)
        parameters['trigger'] = trigger
        response = self.__remote_call('user.tpost', parameters)
        return response.status

    def system_services(self):
        """
        Public API function: system.services: Return a complete list of
        supported services.

        @return
          A list of SystemService objects containing all informations about
          Ping.fm supported services
        """
        parameters = {}
        response = self.__remote_call('system.services', parameters)
        if response.status == OK:
            services = data.find('services')
            service_list = []
            for s in services:
                service = SystemService()
                service.set_id(s.attrib['id'])
                service.set_name(s.attrib['name'])
                service.set_trigger(s.find('trigger').text)
                service.set_url(s.find('url').text)
                service.set_icon(s.find('icon').text)
                service_list.append(service)
            return service_list

    def user_key(self, mobile_key):
        """
        Public API function: user.key: Will exchange a mobile application key
        for a functional application key.  This is for mobile apps that would
        offer an easier way of authenticating users.

        @return
          A User Application's key
        """
        parameters = {}
        parameters['mobile_key'] = mobile_key
        response = self.__remote_call('user.key', parameters)
        if response.status == OK:
            return response.key

    def user_services(self):
        """
        Public API function: user.services: Returns a list of services the
        particular user has set up through Ping.fm.

        @return
          A list of UserService objects containing all informations about
          services the particular user has set up through Ping.fm.
        """
        parameters = {}
        response = self.__remote_call('user.services', parameters)
        if response.status == OK:
            services = data.find('services')
            service_list = []
            for s in services:
                service = UserService()
                service.set_id(s.attrib['id'])
                service.set_name(s.attrib['name'])
                service.set_trigger(s.find('trigger').text)
                service.set_url(s.find('url').text)
                service.set_icon(s.find('icon').text)
                service.set_methods(s.find('methods').text.split(','))
                service_list.append(service)
            return service_list

    def user_triggers(self):
        """
        Public API function: user.triggers: Returns a user's custom triggers.

        @return
          A list of Trigger objects containing all informations about
          user's custom triggers.
        """
        parameters = {}
        response = self.__remote_call('user.triggers', parameters)
        if resposne.status == OK:
            triggers = data.find('triggers')
            trigger_list = []
            for t in triggers:
                trigger = Trigger()
                trigger.set_id(t.attrib['id'])
                trigger.set_method(t.attrib['method'])
                services = t.find('services')
                for s in services:
                    trigger.add_service(s.attrib['id'], s.attrib['name'])
                trigger_list.append(trigger)
            return trigger_list

    def user_latest(self, limit=25, order='DESC'):
        """
        Public API function: user.latest: Returns the last 25 messages a
        user has posted through Ping.fm.

        @return
          A list of Message object containing the last messages a user has
          posted trough Ping.fm
        """
        parameters = {}
        parameters['limit'] = limit
        parameters['order'] = order
        response = self.__remote_call('user.latest', parameters)
        print response.status
        if response.status == OK:
            messages = data.find('messages')
            message_list = []
            # XXX this should probably be moved out to its own private method
            for m in messages:
                message = Message()
                message.set_id(m.attrib['id'])
                message.set_method(m.attrib['method'])
                date = m.find('date')
                message.set_rfc_time(date.attrib['rfc'])
                message.set_unix_time(date.attrib['unix'])
                services = m.find('services')
                for s in services:
                    message.add_service(s.attrib['id'], s.attrib['name'])
                content = m.find('content')
                for c in content:
                    if c.tag == 'body':
                        message.set_body(c.text)
                    if c.tag == 'title':
                        message.set_title(c.text)
                location = m.find('location')
                if location != None:
                    message.set_location(location.text)
                message_list.append(message)
            return message_list
